//cROMFile.cpp
#include "cROMFile.h"

/* Default constructor.
 */
ROMFile::ROMFile()
{
	this->_Create("");
}

/* Constructor with filename to open.
 */
ROMFile::ROMFile(std::string Filename)
{
	this->_Create(Filename);
}

/* PRIVATE: Internal constructor routine.
 * Inputs:
 * -Filename: File to open.
 */
void ROMFile::_Create(std::string Filename)
{
	this->File = NULL;
	if(Filename != "") this->Open(Filename);
}

/* Destructor.
 */
ROMFile::~ROMFile()
{
	if(this->File) fclose(this->File); this->File = NULL;
}

/* Opens a file.
 * Inputs:
 * -Filename: File to open.
 * Returns: true on success, false on failure.
 */
bool ROMFile::Open(std::string Filename)
{
	char Name[32], Chr[16];
	
	this->File = fopen(Filename.c_str(), "rb");
	if(!this->File)
	{
		DebugOut(DO_MAJOR, "Cannot open \"%s\"\n", Filename.c_str());
		return false;
	}
	
	//Read internal name
	this->Read((u8*)Name, 21, 0x81C0);
	Name[21] = '\0';
	for(s32 i=20; (i>=0) && (Name[i] == ' '); i--) Name[i] = '\0'; //Trim spaces
	
	//URL-encode, filtering out all characters that can't be used in file names
	//even on Windows. Also prevent having . as first or last character.
	//Windows is very picky about filenames, and you just know some nut is going
	//to go naming their ROM /."\*?>&. or something.
	this->InternalName = "";
	for(s32 i=0; Name[i]; i++)
	{
		if((Name[i] >= 0x20) && (Name[i] <= 0x7A) && (Name[i] != '.')
		&& (Name[i] != '/') && (Name[i] != '\\') && (Name[i] != '&')
		&& (Name[i] != '?') && (Name[i] != '*') && (Name[i] != ':')
		&& (Name[i] != '|') && (Name[i] != '"') && (Name[i] != '<')
		&& (Name[i] != '>') && ((Name[i] != '.') || (i > 0) || Name[i+1]))
			this->InternalName += Name[i]; //quite a list...
		else
		{
			sprintf(Chr, "%%%02X", Name[i]);
			this->InternalName += Chr;
		}
	}
	DebugOut(DO_STATUS, "Internal name is \"%s\"\n", this->InternalName.c_str());
	
	this->ReadObjNames();
	this->ReadLevelNames();
	return true;
}

/* Reads the default set of object names.
 * Returns: true on success, false on failure.
 */
bool ROMFile::ReadObjNames()
{
	std::string Path = "data/";
	Path += this->InternalName + "/objnames.txt";
	if(this->ReadObjNames(Path)) return true;
	else return this->ReadObjNames("data/objnames.txt");
}

/* Reads object names from the specified file.
 * Returns: true on success, false on failure.
 */
bool ROMFile::ReadObjNames(std::string Filename)
{
	char Buf[8192], *Name;
	u32 TDL, ObjID, SubType, ID, NumRead = 0;
	FILE *F = fopen(Filename.c_str(), "rt");
	if(!F) return false;
	
	this->ObjectName.clear();
	while(fgets(Buf, sizeof(Buf), F))
	{
		if(Buf[0] == ';') continue;
		if(sscanf(Buf, "%X\t%X\t%X\t", &TDL, &ObjID, &SubType) < 3) continue;
		ID = (TDL << 24) | (ObjID << 8) | SubType;
		
		//We need to find the name ourselves because sscanf() fails
		//so read everything after the third tab character
		Name = NULL;
		for(u32 i=0, t=0; Buf[i] && (t < 3); i++)
		{
			if(Buf[i] == '\t') t++;
			if(t == 3) Name = &Buf[i+1];
		}
		if(Name)
		{
			//for some reason names sometimes end up with a line break after them
			//we'll also chop comments while we're here
			for(s32 i=strlen(Name) - 1; i>=0; i--)
				if((Name[i] == '\r') || (Name[i] == '\n') || (Name[i] == ';'))
					Name[i] = '\0';
			//trim the end
			for(s32 i=strlen(Name) - 1; i>=0
			&& ((Name[i] == '\t') || (Name[i] == ' ')); i--) Name[i] = '\0';
			this->ObjectName[ID] = Name;
		}
		NumRead++;
	}
	DebugOut(DO_STATUS, "Read %u object names.\n", NumRead);
	fclose(F);
	return true;
}

/* Reads the default set of level names.
 * Returns: true on success, false on failure.
 */
bool ROMFile::ReadLevelNames()
{
	std::string Path = "data/";
	Path += this->InternalName + "/levelnames.txt";
	if(this->ReadLevelNames(Path)) return true;
	else return this->ReadLevelNames("data/levelnames.txt");
}

/* Reads level names from the specified file.
 * Returns: true on success, false on failure.
 */
bool ROMFile::ReadLevelNames(std::string Filename)
{
	char Buf[8192], *Name;
	u32 ID, NumRead = 0;
	FILE *F = fopen(Filename.c_str(), "rt");
	
	this->LevelName.clear();
	this->LevelName.reserve(512);
	
	//Set names to blank to ensure they all exist, else we'd segfault looking up
	//one that doesn't, because std::vector isn't *that* smart
	//I don't feel std::map is appropriate since I intend to fill in the list,
	//so all 512 entries will be used.
	for(u32 i=0; i<512; i++) this->LevelName.push_back("");
	
	if(!F) return false;
	while(fgets(Buf, sizeof(Buf), F))
	{
		if(Buf[0] == ';') continue;
		if(!sscanf(Buf, "%03X", &ID)) continue;
		
		//We need to find the name ourselves because sscanf() fails
		//so read everything after the tab character
		Name = NULL;
		for(u32 i=0; Buf[i]; i++)
		{
			if(Buf[i] == '\t')
			{
				Name = &Buf[i+1];
				break;
			}
		}
		if(Name)
		{
			//for some reason names sometimes end up with a line break after them
			//we'll also chop comments while we're here
			for(s32 i=strlen(Name) - 1; i>=0; i--)
				if((Name[i] == '\r') || (Name[i] == '\n') || (Name[i] == ';'))
					Name[i] = '\0';
			//trim the end
			for(s32 i=strlen(Name) - 1; i>=0
			&& ((Name[i] == '\t') || (Name[i] == ' ')); i--) Name[i] = '\0';
			this->LevelName[ID] = Name;
		}
		NumRead++;
	}
	DebugOut(DO_STATUS, "Read %u level names.\n", NumRead);
	fclose(F);
	return true;
}


/* Seeks within the file.
 * Inputs:
 * -Addr: Address to seek to.
 */
void ROMFile::Seek(u32 Addr)
{
	fseek(this->File, Addr, SEEK_SET);
}

void ROMFile::Seek(SNESPointer *Ptr)
{
	fseek(this->File, Ptr->ToFileAddr(), SEEK_SET);
}

/* Retrieves current read/write address.
 * Returns: Address.
 */
u32 ROMFile::GetPos()
{
	return ftell(this->File);
}

/* Reads the next byte from the file.
 * Returns: Byte from file.
 */
u8 ROMFile::Read()
{
	u8 B;
	if(!fread(&B, 1, 1, this->File)) return 0;
	return B;
}

/* Reads a byte from the file.
 * Inputs:
 * -Addr: Address to read from.
 * Returns: Byte from file.
 */
u8 ROMFile::Read(u32 Addr)
{
	this->Seek(Addr);
	return this->Read();
}

u8 ROMFile::Read(SNESPointer *Ptr)
{
	this->Seek(Ptr);
	return this->Read();
}

/* Reads several bytes from the file.
 * Inputs:
 * -Buf: Buffer to read into.
 * -Size: Number of bytes to read.
 * Returns: Number of bytes read.
 */
u32 ROMFile::Read(u8 *Buf, u32 Size)
{
	return fread(Buf, 1, Size, this->File);
}

u32 ROMFile::Read(u8 *Buf, u32 Size, u32 Addr)
{
	this->Seek(Addr);
	return this->Read(Buf, Size);
}

u32 ROMFile::Read(u8 *Buf, u32 Size, SNESPointer *Ptr)
{
	this->Seek(Ptr);
	return this->Read(Buf, Size);
}

/* Reads a byte from the file, preserving the read/write address.
 * Inputs:
 * -Addr: Address to read from.
 * Returns: Byte from file.
 */
u8 ROMFile::ReadNoSeek(u32 Addr)
{
	u32 Pos = this->GetPos();
	this->Seek(Addr);
	u8 B = this->Read();
	this->Seek(Pos);
	return B;
}

u8 ROMFile::ReadNoSeek(SNESPointer *Ptr)
{
	u32 Pos = this->GetPos();
	this->Seek(Ptr);
	u8 B = this->Read();
	this->Seek(Pos);
	return B;
}

/* Reads several bytes from the file, preserving the read/write address.
 * Inputs:
 * -Buf: Buffer to read into.
 * -Size: Number of bytes to read.
 * -Addr: Address to read from.
 * Returns: Number of bytes read.
 */
u32 ROMFile::ReadNoSeek(u8 *Buf, u32 Size, u32 Addr)
{
	u32 Pos = this->GetPos();
	this->Seek(Addr);
	u32 R = fread(Buf, 1, Size, this->File);
	this->Seek(Pos);
	return R;
}

u32 ROMFile::ReadNoSeek(u8 *Buf, u32 Size, SNESPointer *Ptr)
{
	u32 Pos = this->GetPos();
	this->Seek(Ptr);
	u32 R = fread(Buf, 1, Size, this->File);
	this->Seek(Pos);
	return R;
}
